import { getChildren } from "../Component";
import { locationName, shaderConfig } from "./Interface";
import Obj3D from "./Obj3D";

export default class Scene {
    domElement: HTMLCanvasElement;
    device?: GPUDevice;
    context?: GPUCanvasContext;
    clearColor: GPUColor;
    children: Obj3D[];

    public static scene: Scene;

    private pipelineMap: Map<string, GPURenderPipeline> = new Map();//shaderMap
    private locationNameMap: Map<string, Map<string, locationName>> = new Map();//locationNameMap映射
    private drawMap: Map<string, Obj3D[]> = new Map();//渲染队列, 根据ShaderMap插入顺序进行渲染

    public constructor(canvas: HTMLCanvasElement, color: GPUColor = { r: 0, g: 0, b: 0, a: 1 }) {
        this.domElement = canvas;
        this.clearColor = color;
        this.children = [];
        Scene.scene = this;

        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        if (!navigator.gpu) {
            console.log('当前浏览器不支持WebGPU');
            return;
        }
    }

    public async init() {
        const { domElement: canvas, clearColor } = this;

        //浏览器请求GPU适配器
        const adapter = await navigator.gpu.requestAdapter();
        //获取GPU设备对象, 通过GPU设备镀锡device的WebGPU API可以控制GPU的渲染过程
        const device = await adapter?.requestDevice() as GPUDevice;

        const context = canvas.getContext('webgpu')!;
        const format = navigator.gpu.getPreferredCanvasFormat();//颜色数据
        context?.configure({
            device: device,//GPU对象
            format: format,//颜色数据
        });

        this.device = device;
        this.context = context;
    }

    public regProgram(config: shaderConfig) {
        const { device, pipelineMap, locationNameMap } = this;
        const { vs, fs, shaderName, attribs, unifroms, mode } = config;


        const vsM = device!.createShaderModule({ code: vs });
        const fsM = device!.createShaderModule({ code: fs });

        const lcationNameArr: Map<string, locationName> = new Map();

        const buffers: Array<GPUVertexBufferLayout> = [];
        attribs.forEach(u => {
            const { size, location, name } = u;
            let _size = 1;
            if (size.lastIndexOf('x') != -1) {
                _size = Number(size.substring(size.length - 1, size.length));
            }
            buffers.push({
                arrayStride: _size * 4,       //一个顶点数据占用的字节长度(三维 * 4)
                attributes: [{
                    shaderLocation: location,   //GPU显存是顶点缓冲区标记存储位置
                    format: size,               //格式: float32x3表示一个顶点数据包含3个3
                    offset: 0                   //偏移量?    
                }],
            });

            lcationNameArr.set(name, u);
        });

        // 创建渲染管线
        const pipeline = device!.createRenderPipeline({
            vertex: {//顶点作色器
                module: vsM,
                entryPoint: 'main',//入口函数
                buffers: buffers
            },
            fragment: {//片元作色器
                module: fsM,
                entryPoint: 'main',//入口函数
                targets: [{
                    format: navigator.gpu.getPreferredCanvasFormat(),
                }]
            },
            primitive: {
                topology: mode,//绘制三角形
            },
            layout: 'auto',
        });

        unifroms.forEach(u => {
            const { name, group } = u;
            u.groupLayout = pipeline.getBindGroupLayout(group);
            lcationNameArr.set(name, u);
        })

        pipelineMap.set(shaderName, pipeline);
        locationNameMap.set(shaderName, lcationNameArr);
    }

    public add(...elements: Obj3D[]) {
        const { device, children, drawMap } = this;
        elements.forEach(u => {
            children.push(u);
            u.parent = this;

            //统一初始化
            let child = getChildren(u);
            child.push(u);
            for (let obj of child) {
                obj.init(device!);
                this.addDrawMap(obj);
            }
        });
    }

    //如果父类对象已经加入scene中后再向父类对象添加元素, 则需要手动调用一次
    public addDrawMap(element: Obj3D) {
        const { drawMap } = this;
        if (element.mat == null)
            return;

        const key = element.mat.shader;
        //查询key是否存在
        if (typeof drawMap.get(key) == 'undefined') {
            drawMap.set(key, [element]);
        } else {
            drawMap.get(key)!.push(element);
        }
    }

    public remove(element: Obj3D) {
        const { children, drawMap } = this;
        const i = children.indexOf(element);
        if (i != -1) {
            children.splice(i, 1);

            let child = getChildren(element);
            child.push(element);
            for (let obj of child) {
                this.removeDrawMap(obj);
            }
        }
    }

    private removeDrawMap(element: Obj3D) {
        const { drawMap } = this;
        if (element.mat == null)
            return;
        const key = element.mat.shader;
        const data = drawMap.get(key);
        if (!(typeof data == 'undefined')) {
            const index = data.indexOf(element);
            data.splice(index, 1);
        }
    }

    public setAttrib(name: string, array: Float32Array) {
        const { children } = this;
        children.forEach(u => {
            u.setAttrib(name, array);
        })
    }

    public setUniform(name: string, array: Float32Array) {
        const { children } = this;
        children.forEach(u => {
            u.setUniform(name, array);
        })
    }

    public draw() {
        const {
            device,
            drawMap, pipelineMap, locationNameMap,
        } = this;

        const draw = (renderPass: GPURenderPassEncoder) => {
            for (let [key, value] of drawMap) {
                const pipeline = pipelineMap.get(key);
                if (pipeline) {

                    renderPass.setPipeline(pipeline);

                    const locationNames = locationNameMap.get(key);
                    if (locationNames) {
                        value.forEach(element => {
                            element.update(device!, renderPass, locationNames);
                            element.draw(renderPass);
                        });
                    } else {
                        console.log(`[${key}]为空`);
                    }
                }
            }
        }
        this.createRenderPass(draw);
    }

    private createRenderPass(draw: (renderPass: GPURenderPassEncoder) => void) {
        const { device, context, clearColor } = this;
        // 创建命令编码器
        const commandEncoder = device!.createCommandEncoder();
        // 创建渲染通道对象
        const renderPass = commandEncoder.beginRenderPass({
            colorAttachments: [
                {
                    //指向用于Canvas画布纹理视图对象(Canvas对应的缓冲区)
                    //该渲染管道renderPass输出的像素会存储到Canvas对应的颜色缓冲区
                    view: context!.getCurrentTexture().createView(),
                    storeOp: 'store',//像素数据写入颜色缓冲区
                    loadOp: 'clear',//每次渲染前执行清除?
                    clearValue: clearColor,//背景颜色
                }
            ]
        });

        draw(renderPass);

        renderPass!.end();
        //创建命令缓冲区
        const commandBuffer = commandEncoder!.finish();
        //生成GPU指令存入缓冲区
        device!.queue.submit([commandBuffer]);
    }


    public getType() {
        return Scene.name;
    }

}